package com.zhss.dfs.client;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.zhss.rpc.model.NameNodeModel;
import com.zhss.rpc.service.NameNodeServiceGrpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;

/**
 * @description:
 * @Author liu Weidong
 * @Email:
 * @Date: 2024/4/28 18:27
 * @Version 1.0
 */
public class FileSystemImpl implements FileSystem {
    public static final Integer STATUS_SUCCESS = 1;
    public static final Integer STATUS_FAILURE = 2;
    public static final Integer STATUS_SHUTDOWN = 3;
    public static final Integer NAME_REPEAT = 4;


    private final ManagedChannel channel;
    private final NameNodeServiceGrpc.NameNodeServiceBlockingStub blockingStub;

    public static final String HOST = "localhost";

    public static final Integer PORT = 50072;

    // 构造函数，初始化 gRPC 通道和服务存根
    public FileSystemImpl() {
        this.channel = ManagedChannelBuilder.forAddress(HOST, PORT)
                .usePlaintext()
                .build();
        this.blockingStub = NameNodeServiceGrpc.newBlockingStub(channel);
    }

    @Override
    public Boolean mkdir(String path) throws Exception {
        NameNodeModel.MkdirRequest mkdirRequest = NameNodeModel.MkdirRequest.newBuilder().setPath(path).build();
        NameNodeModel.MkdirResponse mkdirResponse = blockingStub.mkdir(mkdirRequest);
        System.out.println("创建目录响应： " + mkdirResponse.getMessage());
        return true;
    }

    @Override
    public Boolean shutDown() throws Exception {
        NameNodeModel.ShutDownRequest shutDownRequest = NameNodeModel.ShutDownRequest.newBuilder().setCode(1).build();
        NameNodeModel.ShutDownResponse shutDownResponse = blockingStub.shutDown(shutDownRequest);
        System.out.println("创建目录响应： " + shutDownResponse.getStatus());
        return true;
    }

    @Override
    public boolean upload(String fileName, byte[] bytes, Long fileSize) throws Exception {
        // 判断目录是否存在，不存在，不存在创建目录
        if (!createFile(fileName)) {
            return false;
        }
        // 查询可用的dataNode
        JSONArray jsonArray = allocateDateNode(fileSize, fileName);
        JSONArray hasSuccessArray = new JSONArray();
        NIOClient nioClient = new NIOClient();
        for (Object o : jsonArray) {
            JSONObject jsonObject = (JSONObject) o;
            String hostname = jsonObject.getString("hostname");
            Integer port = jsonObject.getInteger("port");
            Boolean result = nioClient.sendFile(fileName, bytes, fileSize, hostname, port);
            if (!result) {
                // 没有成功。需要调用nameNode进行重新分配节点
                JSONObject newDataNode = reallocateDataNode(fileSize, jsonObject.toJSONString());
                if (newDataNode == null) {
                    throw new Exception("上传失败");
                }
                hostname = newDataNode.getString("hostname");
                port = jsonObject.getInteger("port");
                nioClient.sendFile(fileName, bytes, fileSize, hostname, port);
            }
        }
        return false;
    }

    private JSONObject reallocateDataNode(Long fileSize, String excludeDataNode) {
        NameNodeModel.ReallocateDataNodeRequest reallocateDataNodeRequest = NameNodeModel.ReallocateDataNodeRequest.newBuilder()
                .setFileSize(fileSize)
                .setExcludeDataNode(excludeDataNode)
                .build();
        NameNodeModel.ReallocateDataNodeResponse response = blockingStub.reallocateDataNode(reallocateDataNodeRequest);
        int status = response.getStatus();
        if (status == STATUS_FAILURE) {
            return null;
        }
        String dataNode = response.getDataNode();
        return JSONObject.parseObject(dataNode);
    }

    @Override
    public byte[] download(String fileName) throws Exception {
        JSONObject dataNodeInfo = getDataNode(fileName);
        String hostname = dataNodeInfo.getString("hostname");
        Integer port = dataNodeInfo.getInteger("port");
        NIOClient nioClient = new NIOClient();
        byte[] bytes = null;
        try {
            bytes = nioClient.readFile(fileName, hostname, port);
        } catch (Exception e) {
            // 下载发生异常重新选择其他副本节点进行下载
            dataNodeInfo = chooseOtherhReplicaDataNode(fileName,dataNodeInfo.toString());
            try {
                hostname = dataNodeInfo.getString("hostname");
                port = dataNodeInfo.getInteger("port");
                bytes = nioClient.readFile(fileName, hostname, port);
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
        System.out.println("下载文件字节长度为；" + bytes.length);
        return bytes;
    }

    private JSONObject chooseOtherhReplicaDataNode(String fileName, String excludeDataNode) {
        NameNodeModel.ChooseOtherDataNodeRequest request = NameNodeModel.ChooseOtherDataNodeRequest.newBuilder()
                .setFileName(fileName)
                .setExcludeDataNode(excludeDataNode)
                .build();
        NameNodeModel.ChooseOtherDataNodeResponse response = blockingStub.chooseOtherDataNode(request);
        String dataNode = response.getDataNode();
        return JSONObject.parseObject(dataNode);
    }

    private JSONObject getDataNode(String fileName) {
        NameNodeModel.GetDataForFileRequest getDataForFileRequest = NameNodeModel.GetDataForFileRequest.newBuilder().setFileName(fileName).build();
        NameNodeModel.GetDataForFileResponse response = blockingStub.getDataNodeForFile(getDataForFileRequest);
        String dataNodeInfo = response.getDataNodeInfo();
        return JSONObject.parseObject(dataNodeInfo);
    }

    private Boolean createFile(String fileName) {
        NameNodeModel.CreateFileRequest createFileRequest = NameNodeModel.CreateFileRequest.newBuilder().setFileName(fileName).build();
        NameNodeModel.CreateFileResponse createFileResponse = blockingStub.createFile(createFileRequest);
        int status = createFileResponse.getStatus();
        if (status == NAME_REPEAT) {
            String errorMsg = createFileResponse.getMsg();
            System.out.println(errorMsg);
            return false;
        }
        return true;
    }

    private JSONArray allocateDateNode(Long fileSize, String fileName) {
        NameNodeModel.AllocateDataNodeRequest allocateDataNodeRequest = NameNodeModel.AllocateDataNodeRequest.newBuilder().setFileName(fileName).setFileSize(fileSize).build();
        NameNodeModel.AllocateDataNodeResponse allocateDataNodeResponse = blockingStub.allocateDataNode(allocateDataNodeRequest);
        String allocateDateNodes = allocateDataNodeResponse.getAllocateDateNodes();
        System.out.println("可分配的dataNode节点为：" + allocateDateNodes);
        return JSONArray.parseArray(allocateDateNodes);
    }
}
